package com.zhglxt.activiti.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.zhglxt.activiti.entity.ProcessDefinitionDto;
import com.zhglxt.activiti.service.ActProcessService;
import com.zhglxt.common.core.entity.AjaxResult;
import com.zhglxt.common.core.entity.sys.SysUser;
import com.zhglxt.common.core.page.TableDataInfo;
import com.zhglxt.common.core.text.Convert;
import com.zhglxt.common.utils.StringUtils;
import com.zhglxt.system.service.ISysUserService;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

/**
 * 流程管理 服务层实现层
 *
 * @author liuwy
 */
@Service
public class ActProcessServiceImpl implements ActProcessService {
    @Autowired
    RepositoryService repositoryService;

    @Autowired
    RuntimeService runtimeService;

    @Autowired
    HistoryService historyService;

    @Autowired
    ISysUserService userService;

    @Autowired
    protected ObjectMapper objectMapper;

    private final Logger logger = LoggerFactory.getLogger(ActProcessServiceImpl.class);


    /**
     * 使用部署对象ID查看流程图
     *
     * @param deploymentId 部署id
     * @param imageName    资源文件名
     * @return 文件流
     */
    @Override
    public InputStream findImageStream(String deploymentId, String imageName) throws Exception {
        return repositoryService.getResourceAsStream(deploymentId, imageName);
    }

    /**
     * 部署流程定义文件
     *
     * @param files 多个文件，支持zip和bar类型的流程压缩文件，以及支持.bpmn和.bpmn20.xml后缀的流程定义文件
     *              压缩的和非压缩的流程文件同时分别部署，互不影响
     * @return 返回部署成功后的流程ID
     */
    @Override
    public AjaxResult deployemntProcessFiles(List<MultipartFile> files, String category) {
        // 将压缩格式文件与流程文件及流程文件对应的图片分别部署
        List<MultipartFile> comparessFiles = new ArrayList<>();
        List<MultipartFile> bpmnFiles = new ArrayList<>();
        try {
            for (MultipartFile file : files) {
                String fileName = file.getOriginalFilename();
                if (fileName.contains(".zip") || fileName.contains(".bar")) {
                    comparessFiles.add(file);
                } else if (fileName.contains(".bpnm20.xml") || fileName.contains(".bpmn")) {
                    bpmnFiles.add(file);
                } else {
                    // 获取文件扩展名
                    String extension = FilenameUtils.getExtension(fileName);

                    throw new Exception("不支持的文件类型：" + extension);

                }
            }
            List<AjaxResult> comparessFilesResults = new ArrayList<>();
            List<AjaxResult> bpmpFilesResults = new ArrayList<>();

            for (MultipartFile comparessFile : comparessFiles) {
                InputStream fileInputStream = comparessFile.getInputStream();
                String fileName = comparessFile.getOriginalFilename();

                AjaxResult ajaxResult = saveNameDeplove(fileInputStream, fileName, category);
                comparessFilesResults.add(ajaxResult);
            }

            for (MultipartFile bpmnFile : bpmnFiles) {
                String fileName = bpmnFile.getOriginalFilename();
                InputStream inputStream = bpmnFile.getInputStream();
                AjaxResult ajaxResult = saveNameDeplove(inputStream, fileName, category);
                bpmpFilesResults.add(ajaxResult);
            }
            AjaxResult result = AjaxResult.success();
            result.put("压缩文件部署后的结果", comparessFilesResults);
            result.put("bpmn和xml件部署后的结果", bpmpFilesResults);
//            logger.info("测试返回结果：", result);
            return result;
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }

    }

    /**
     * 部署上传的流程文件
     *
     * @param is       文件流
     * @param fileName 文件名称
     * @param category 类型
     * @return 结果
     */
    @Override
    public AjaxResult saveNameDeplove(InputStream is, String fileName, String category) {
        ZipInputStream zipInputStream = null;
        try {
//            logger.info("要部署的流程文件名是：{}", fileName);
            // 获取文件扩展名
            String extension = FilenameUtils.getExtension(fileName);
//            logger.info("要部署的流程文件的扩展名是：{}", extension);
            Deployment deployment = null;
            if (extension.equals("zip")) {
                zipInputStream = new ZipInputStream(is);
                // 创建流程定义

                deployment = repositoryService.createDeployment().name(fileName)
                        .addZipInputStream(zipInputStream).deploy();


            } else if (extension.equals("bpmn") || extension.equals("xml") || extension.equals("bpnm20.xml")) {

                deployment = repositoryService.createDeployment().name(fileName)
                        .addInputStream(fileName, is).deploy();
            } else {
                throw new Exception("不支持的文件类型：" + extension);
            }


            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .deploymentId(deployment.getId()).singleResult();
            repositoryService.setProcessDefinitionCategory(processDefinition.getId(), category);
            return AjaxResult.success(StringUtils.format("部署成功，流程编号[{}]", processDefinition.getId()));

        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
    }


    //===========================================================================================

    /**
     * 查询已部署的流程定义列表
     *
     * @param processDefinitionDto 流程信息
     * @return 流程集合
     */
    @Override
    public TableDataInfo selectProcessDefinitionList(ProcessDefinitionDto processDefinitionDto) {
        TableDataInfo data = new TableDataInfo();

        ProcessDefinitionQuery pdQuery = repositoryService.createProcessDefinitionQuery();

        if (StringUtils.isNotEmpty(processDefinitionDto.getKey())) {
            pdQuery.processDefinitionKey(processDefinitionDto.getKey());
        }
        if (StringUtils.isNotEmpty(processDefinitionDto.getName())) {
            pdQuery.processDefinitionName(processDefinitionDto.getName());
        }
        if (StringUtils.isNotEmpty(processDefinitionDto.getDeploymentId())) {
            pdQuery.deploymentId(processDefinitionDto.getDeploymentId());
        }

        data.setTotal(pdQuery.count());


        List<ProcessDefinition> processDefinitions = pdQuery.orderByDeploymentId().desc().listPage(processDefinitionDto.getPageNum(), processDefinitionDto.getPageSize());

        List<ProcessDefinitionDto> processDefinitionDtos = new ArrayList<>();

        ProcessDefinitionDto processDefinitionDto1 = null;
        Deployment deployment = null;
        String deploymentId = null;
        for (ProcessDefinition processDefinition : processDefinitions) {
//            logger.info("processDefinition:{}",processDefinition);

            deploymentId = processDefinition.getDeploymentId();
            deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
            Date deploymentTime = deployment.getDeploymentTime();
//            logger.info("deploymentTime:{}",deploymentTime);
            // 设置流程部署时间
            processDefinitionDto1 = new ProcessDefinitionDto(processDefinition);
            processDefinitionDto1.setDeploymentTime(deploymentTime);
//            logger.info("processDefinitionDto:{}",processDefinitionDto1);


            processDefinitionDtos.add(processDefinitionDto1);

        }


//        data.setRows(pdQuery.orderByDeploymentId().desc()
//                .listPage(processDefinitionDto.getPageNum(), processDefinitionDto.getPageSize()).stream()
//                .map(ProcessDefinitionDto::new).collect(Collectors.toList()));

        data.setRows(processDefinitionDtos);

        return data;
    }

    /**
     * 查询运行中的流程实例
     *
     * @param processDefinitionDto
     * @return
     */
    @Override
    public TableDataInfo selectProcessInstanceRunningList(ProcessDefinitionDto processDefinitionDto) {
//        logger.info("查询运行中的流程实例查询参数：{}", processDefinitionDto);
//        logger.info("查看运行中的流程：");
        TableDataInfo tableDataInfo = new TableDataInfo();

        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
        // 设置流程实例查询条件
        if (StringUtils.isNotBlank(processDefinitionDto.getProcessInstanceId())) {
            processInstanceQuery.processInstanceId(processDefinitionDto.getProcessInstanceId());
        }
        if (StringUtils.isNotBlank(processDefinitionDto.getKey())) {
            processInstanceQuery.processDefinitionKey(processDefinitionDto.getKey());
        }

        long count = processInstanceQuery.count();
        tableDataInfo.setTotal(count);

        List<ProcessInstance> processInstances = processInstanceQuery.orderByProcessInstanceId().desc().listPage(processDefinitionDto.getPageNum(), processDefinitionDto.getPageSize());

        List<ProcessDefinitionDto> processRunnings = new ArrayList<>();
        ProcessDefinitionDto processDefinitionDto1 = null;
        for (ProcessInstance processInstance : processInstances) {

            /**
             * 设置流程实例信息
             */
            processDefinitionDto1 = new ProcessDefinitionDto(processInstance);

            // 查询流程发起人
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
            String startUserInfo = historicProcessInstance.getStartUserId();
            Date startTime = historicProcessInstance.getStartTime();
            logger.info("流程发起人id是：{}", startUserInfo);
            // 查询发起人信息
            SysUser startUser = userService.selectUserById(startUserInfo);
            logger.info("发起人信息是：{}", startUser);
            processDefinitionDto1.setStartableByUser(startUser.getUserName());
            processDefinitionDto1.setProcessInstanceStartTime(startTime);
            logger.info("流程发起时间是：{}", startTime);
            processRunnings.add(processDefinitionDto1);
        }

        tableDataInfo.setRows(processRunnings);
        return tableDataInfo;
    }
//
//    /**
//     * 查看运行中的流程
//     */
//    @Override
//    public List<Map<String, Object>> runningList(Map<String, Object> paramMap) {
//        logger.info("查看运行中的流程--old");
//        List<Map<String, Object>> listMap = new ArrayList<>();
//
//        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
//        // 设置流程实例查询条件
//        if (StringUtils.isNotBlank(paramMap.get("processId"))) {
//            processInstanceQuery.processInstanceId(paramMap.get("processId").toString());
//        }
//        if (StringUtils.isNotBlank(paramMap.get("procDefKey"))) {
//            processInstanceQuery.processDefinitionKey(paramMap.get("procDefKey").toString());
//        }
//        int firstResult = (Integer.parseInt(paramMap.get("pageNum").toString()) - 1) * Integer.parseInt(paramMap.get("pageSize").toString());
//        if (firstResult >= processInstanceQuery.count()) {
//            firstResult = 0;
//        }
//        int maxResults = Integer.parseInt(paramMap.get("pageSize").toString());
//        List<ProcessInstance> list = processInstanceQuery.listPage(firstResult, maxResults);
//
//
//        // 循环遍历
//        for (ProcessInstance pi : list) {
//            HashMap<String, Object> piMap = Maps.newHashMap();
//            piMap.put("id", pi.getId());
//            piMap.put("name", pi.getName());
//            piMap.put("parentId", pi.getParentId());
//            piMap.put("processDefinitionId", pi.getProcessDefinitionId());
//            piMap.put("processDefinitionKey", pi.getProcessDefinitionKey());
//            piMap.put("processDefinitionName", pi.getProcessDefinitionName());
//            piMap.put("processInstanceId", pi.getProcessInstanceId());
//            piMap.put("tenantId", pi.getTenantId());
//            piMap.put("processDefinitionVersion", pi.getProcessDefinitionVersion());
//            piMap.put("processVariables", pi.getProcessVariables());
//            piMap.put("suspended", pi.isSuspended());
//            piMap.put("activityId", pi.getActivityId());
//            piMap.put("businessKey", pi.getBusinessKey());
//            piMap.put("deploymentId", pi.getDeploymentId());
//            piMap.put("description", pi.getDescription());
//            piMap.put("localizedDescription", pi.getLocalizedDescription());
//            piMap.put("localizedName", pi.getLocalizedName());
//            listMap.add(piMap);
//        }
//        return listMap;
//    }

    /**
     * 根据 流程实例ID 删除流程实例
     */
    @Transactional(readOnly = false)
    @Override
    public boolean deleteProcIns(Map<String, Object> paramMap) {

        boolean result = true;
        try {
            String ids = paramMap.get("ids").toString();
            String[] strings = Convert.toStrArray(ids);
//            logger.info("根据 流程实例ID 删除流程实例");
//            logger.info("要删除的id是：{}", ids);
            for (String procInsID : strings) {
                runtimeService.deleteProcessInstance(procInsID, "");
            }

        } catch (Exception e) {
            result = false;
            throw e;
        }
        return result;
    }

    /**
     * 根据已部署流程的部署ID，删除流程定义
     *
     * @param ids 部署ids
     * @return 结果
     */
    @Override
    public boolean deleteProcessDefinitionByDeploymentIds(String ids) {
//        logger.info("根据流程部署id，删除流程定义");
//        logger.info("要删除的id是：{}", ids);
        boolean result = true;
        try {
            String[] deploymentIds = Convert.toStrArray(ids);
            for (String deploymentId : deploymentIds) {
                // 级联删除，不管流程是否启动，都能可以删除
                repositoryService.deleteDeployment(deploymentId, true);

            }
        } catch (Exception e) {
            result = false;
            throw e;
        }
        return result;
    }


    /**
     * 挂起、激活流程实例，对流程的所有实例有效
     * 流程定义为挂起状态该流程定义将不允许启动新的流程实例，同时该流程定义下所有的流程实例将
     * 全部挂起暂停执行，更新ACT_RE_PROCDEF表
     */
    @Transactional(readOnly = false)
    @Override
    public boolean updateState(String state, Map<String, Object> paramMap) {

        boolean flag = false;
        if (state.equals("active")) {
            // 激活流程
            repositoryService.activateProcessDefinitionById(paramMap.get("procDefId").toString(), true, null);
            flag = true;
        } else if (state.equals("suspend")) {
            // 挂起，终止流程
            repositoryService.suspendProcessDefinitionById(paramMap.get("procDefId").toString(), true, null);
            flag = true;
        }
        return flag;
    }

    /**
     * 将流程定义转换成模型
     * 查询到指定ID的流程的流程定义，转换可编辑资源后保存到模型表中
     *
     * @param processId 流程编号
     * @return 模型数据
     * @throws Exception
     */
    @Override
    public Model convertToModel(String processId) throws Exception {
//        logger.info("将流程定义转换成模型");
        // 根据已部署流程ID查询指定的流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(processId).singleResult();
        /**
         * 转换
         */
        // 根据流程部署ID和流程资源名称获取流程资源文件的资源流
        InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
                processDefinition.getResourceName());

        XMLInputFactory xif = XMLInputFactory.newInstance();
        // 包装流，指定编码格式
        InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
        XMLStreamReader xtr = xif.createXMLStreamReader(in);
        BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);

        BpmnJsonConverter converter = new BpmnJsonConverter();
        // 转换后的要保存的可编辑资源
        ObjectNode editorNode = converter.convertToJson(bpmnModel);

        /**
         * 保存信息
         */
        // 要保存的模型，保存到数据库中
        Model modelData = repositoryService.newModel();

        modelData.setKey(processDefinition.getKey());
        //modelData.setName(processDefinition.getResourceName());
        modelData.setName(processDefinition.getName());
        modelData.setCategory(processDefinition.getCategory());
        modelData.setDeploymentId(processDefinition.getDeploymentId());
        modelData.setVersion(Integer.parseInt(
                String.valueOf(repositoryService.createModelQuery().modelKey(modelData.getKey()).count() + 1)));
        //设置模型mateinfo,act_re_model表metainfo字段
        ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processDefinition.getName());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, modelData.getVersion());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription());
        modelData.setMetaInfo(modelObjectNode.toString());

        // 保存模型信息基本属性
        repositoryService.saveModel(modelData);
        // 保存模型的编辑资源信息--modeNode
        repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));

        return modelData;
    }

}
